<?php
/**
* xml-rpc server
* @author J.Ducastel <fredbird@fredbird.org>
* @version
* @uses domxml
*/
class ooXmlRpcServer {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    */
    function ooXmlRpcServer($iniFile=null) {
        $this->__construct($iniFile);
    }/**/

    /**
    *
    * @access
    */
    function __construct($iniFile=null) {
        //$this->deb=&wak::getservice('wakdebug');
        $this->loadConfig($iniFile);
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * Tries to serve current http xml-rpc method call
    * @access public
    * @return bool
    */
    function serveRequest() {
        //$this=&domXmlRpcServer::_getsingleton();
        // check for http authentication
        if (!$this->authenticationCheck($this->cfg['users'])) {
            // return $this->raiseFa
            return false;
        }
        // retrieve and build method call
        if (!$call=&$this->getMethodCall()) {
            // failed, fault have been raised by getMethodCall
            // echo "invalid call : ".$this->deb->vardump($call);
            return false;
        }
        // runs call
        // echo $this->deb->vardump($this);
        if (!$response=&$this->ResponseToCall($call)) {
            // failed, raise fault
            return false;
        }
        // echo $deb->vardump($cm);
        // sending response
        return $this->sendResponse($response);
    }/**/

    /**
    * build and send methodResponse including fault
    * @param int $code faultCode
    * @param string $string faultString
    * @access public
    * @return bool
    */
    function raiseFault($code,$string=null) {
        // has response already been sent ?
        if ($this->responseSent)
            return false;
        // getting predefined desc if required and available
        if (is_null($string) and isset($this->cfg['faults'][$code]))
            $string=$this->cfg['faults'][$code];
        $fault=&new ooXmlRpcFault($code,$string);
        if (!$fault->isValid()) {
            return false;
        }
        $response=&new ooXmlRpcMethodResponse();
        //$responseNode=&$response->getNode();
        //$fault->appendToNode($responseNode);
        $response->setContent($fault);
        return $this->sendResponse($response);
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var array configuration data
    * 'service_classes'=> array('serviceName'=>'class')
    * 'service_files'=> array('serviceName'=>'file')
    */
    var $cfg=array(
        // http authentication config
        'auth'=>array(
            'use'=>false
            , 'pwd_file'=>'')
        , 'users'=>array()
        // classes associated to services
        , 'service_classes'=>array()
        // files defining service classes
        , 'service_files'=>array()
        // default faultstring for fault codes
        , 'faults'=>array(
            400=>'bad request'
            , 401=>'unauthorized'
            , 403=>'forbidden'
            , 404=>'not found'
            , 500=>'internal server error'
            , 501=>'not implemented'
            , 503=>'service unavailable'));
    /**
    * @var bool has methodResponse be sent ?
    */
    var $responseSent=false;

    /**
    * @var type desc
    */
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    * build response to call
    * find and execute call's implementation
    * @param ooxmlrpcmethodcall &$call
    * @access protected
    * @return ooxmlrpcmethodresponse
    */
    function & responseToCall(&$call) {
        // $deb=&wak::getservice('wakdebug');
        $response=false;
        if (!is_a($call,'ooxmlrpcmethodcall')) {
            // bad argument
            $this->raiseFault(500);
            return $response;
        }
        $method=$call->getmethod(); //echo "<p>method: $method</p>";
        $service=$call->getServiceName(); //echo "<p>service: $service</p>";
        // get params
        $params=&$call->getParams();
        // build response
        $response=&new ooXmlRpcMethodResponse();
        // is call service based ?
        if ($service) {
            // include service file
            if (isset($this->cfg['service_files'][$service])) {
                $files=explode(' ',$this->cfg['service_files'][$service]);
                foreach ($files as $file) {
                    if (!file_exists($file)) {
                        // trigger_error("ooXmlRpcServer : required file $file don't exist",E_USER_WARNING);
                        // return false;
                    }
                    include_once($file);
                }
            }
            // get service class
            if (isset($this->cfg['service_classes'][$service]))
                $class=$this->cfg['service_classes'][$service];
            else $class=$service;
            // is class defined ?
            if (!class_exists($class)) {
                // not defined, raising fault
                $str="ooXmlRpcServer : class $class undefined";
                $this->raiseFault(503,$str);
                return false;
            }
            // is method defined ?
            if (!in_array($method,get_class_methods($class))) {
                // method not implemented, raising fault
                $this->raiseFault(501);
                return false;
            }
            // run class method
            $result=eval('return '.$class.'::'.$method.'($params);');
            if (!$response->setContent($result)) {
                // invalid result, raise fault
                //echo '<p>invalid result : '.$deb->vardump($result).'</p>';
                $this->raiseFault('500','bad method result');
            }
            //$deb=&wak::getservice('wakdebug');
            //echo $deb->vardump($result);
            // echo wakaffvar($response);
        } else {
            // searching within method_functions
            // searching within own methods
            // not found
            // method not implemented, raising fault
            $this->raiseFault(501);
            return false;
        }
        return $response;
    }/**/

    /**
    * retrieves and build method call from http raw post data
    * @param
    * @access protected
    * @return domXmlRpcMethodCall|false
    */
    function & getMethodCall() {
        // echo "getting call !";
        //$this=&domXmlRpcServer::_getsingleton();
        $call=false;
        if (!$xml=&$this->getrawpost()) {
            // no data at all,  raising fault
            $this->raiseFault(400,'no xml data');
            return $call;//
        }
        // building call instance
        $call=& new ooXmlRpcMethodCall();
        if (!$call->setFromXml($xml)) {
            // failed to retrieve xml, raising fault
            $this->raiseFault(400,'invalid xml data');
            $call=false;
        }
        return $call;
    }/**/

    /**
    * returns raw post data
    * @param
    * @access protected
    * @return string|false
    */
    function getRawPost() {
        if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])
        or !$raw=$GLOBALS['HTTP_RAW_POST_DATA']) {
            // hack to retrieve raw data
            $raw=file_get_contents('php://input');
        }
        return $raw;
    }/**/

    /**
    * sends method response to client
    * @param
    * @access protected
    * @return
    */
    function sendResponse(&$response) {
        if ($this->responseSent)
            return false;
        $xml=$response->getxml();
        // sending headers
        header('Content-type: text/xml');
        // sending content
        //$deb=&wak::getservice('wakdebug');
        //echo $deb->vardump($xml,'html',5000);
        echo $xml;
        $this->responseSent=true;
        return true;
    }/**/

    /**
    * loads config from ini file, merge data into $this->cfg
    * search ini file at cfg/{serverclass}.cfg.ini
    * @param string $file
    * @access protected
    * @return bool
    */
    function loadConfig($file=null) {
        if (is_null($file))
            $file='cfg/'.get_class($this).'.cfg.ini'; //echo $file;
        if (file_exists($file) and $cfg=parse_ini_file($file,true)) {
            // merging new config with default one
            $this->cfg=array_merge_recursive($this->cfg,$cfg);
            //trigger_error("loaded config from $file");
            return true;
        }
        return false;
    }/**/

    /**
    * checks for http authentication
    * @param array users user => pwd list of authorized users with pwd
    * @access protected
    * @return bool
    */
    function authenticationCheck($users=array()) {
        // is authentication required ?
        if (!$this->cfg['auth']['use'])
            return true;
        // are login/pwd provided ?
        if (!isset($_SERVER['PHP_AUTH_USER'])
        or !isset($_SERVER['PHP_AUTH_PW'])) {
            $realm='realm';
            //header( 'WWW-Authenticate: Basic realm="'.$realm.'"' );
            header( 'WWW-Authenticate: Basic' );
            header( 'HTTP/1.0 401 Unauthorized' );
            //echo 'Authorization Required.';
            $this->raiseFault(401);
            return false;
            exit;
        } else {
            // checking for provided login/pwd
            $provided_login=$_SERVER['PHP_AUTH_USER'];
            $provided_pwd=$_SERVER['PHP_AUTH_PW'];
            // searching for user/pwd
            foreach ($users as $login => $pwd) {
                if ($login==$provided_login and $pwd==$provided_pwd)
                    return true;
            }
            // invalid user/pwd
            $this->raiseFault(401);
            return false;
        }
        
        return false;
    }/**/
}
?>
